/*
*Copyright (c) Nikhil Jain.
*Project ICARUS, communications and 
*artificial intelligence.
*/



/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
 */
package javadrone.client;

//~--- non-JDK imports --------------------------------------------------------

import com.lti.civil.CaptureException;

import javadrone.Drone;
import javadrone.Server;

import javadrone.client.simulation.BotSimulator;
import javadrone.client.simulation.GenericCommandExecutor;
import javadrone.client.simulation.GenericDroneQuery;
import javadrone.client.simulation.VirtualBotSimulator;

import javadrone.command.CommandExecutor;

//~--- JDK imports ------------------------------------------------------------

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import java.net.InetAddress;

import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;

import java.security.AccessControlException;

import java.util.ArrayList;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 *
 * @author Nikzzay
 */
public class DroneClient {
    private static long    TIME_DIFF;
    private static Context namingContext;
    private static Server  server;

    static {
        try {
            namingContext = new InitialContext();
        } catch (NamingException e) {}
    }

    public DroneClient(String policyFile) throws NamingException {
        if (server == null) {
            try {
                assignServer();
                TIME_DIFF = server.getTime() - System.currentTimeMillis();
            } catch (RemoteException ex) {
                Logger.getLogger(DroneClient.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        System.setProperty("java.security.policy", policyFile);
        System.setSecurityManager(new RMISecurityManager());
    }

    public DroneClient(String policyFile, String serverURL) throws NamingException {
        server = (Server) namingContext.lookup("rmi://" + serverURL + "/" + "DRONE SERVER");

        try {
            TIME_DIFF = server.getTime() - System.currentTimeMillis();
        } catch (RemoteException ex) {
            Logger.getLogger(DroneClient.class.getName()).log(Level.SEVERE, null, ex);
        }

        System.setProperty("java.security.policy", policyFile);
        System.setSecurityManager(new RMISecurityManager());
    }

    public Drone lookupDrone(String droneName) throws NamingException {
        try {
            return server.registerDrone(droneName);
        } catch (RemoteException ex) {
            return null;
        }
    }

    private static void assignServer() {
        try {
            System.out.println("Assigning server...");

            ArrayList<String> networkMachines = getNetworkMachines();

            System.out.println(System.getProperty("user.dir") + "\\build\\classes\\javadrone\\client\\client.policy");

            try {
                System.out.println("\nList of potential servers: " + networkMachines);
            } catch (Exception ex) {
                ex.printStackTrace();
            }

            try {
                server = (Server) namingContext.lookup("rmi://" + networkMachines.get(0) + "/" + "DRONE SERVER");
            } catch (NamingException ex) {
                server = null;
            }
        } catch (IOException | InterruptedException | BrokenBarrierException ex) {
            Logger.getLogger(DroneClient.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static long TIMEDIFF() {
        return TIME_DIFF;
    }

    public static void main(String args[]) {
        try {
            DroneClient dc;

            try {
                dc = new DroneClient(System.getProperty("user.dir")
                                     + "\\build\\classes\\javadrone\\client\\client.policy", "localhost");
            } catch (NamingException nex) {
                dc = new DroneClient(System.getProperty("user.dir")
                                     + "\\build\\classes\\javadrone\\client\\client.policy");
            }

            if (server != null) {
                System.out.println("Time difference is: " + TIMEDIFF());

                Drone           testDrone = (Drone) dc.lookupDrone("TestDrone");
                CommandExecutor ce        = new GenericCommandExecutor(testDrone.getState());

                // VirtualBotSimulator vbs= new VirtualBotSimulator(testDrone, ce, 1000);
                BotSimulator bs = new BotSimulator(testDrone, ce, 100);

                ce.attach(bs);

                DroneCommander dCommander = new DroneCommander(testDrone, ce, new GenericDroneQuery(bs), false);

                System.out.println("Commander starting...");
                dCommander.start();
                System.out.println("Commander started...");
            } else {
                System.out.println("Could not find any servers to connect to...");
            }
        } catch (CaptureException ex) {
            Logger.getLogger(DroneClient.class.getName()).log(Level.SEVERE, null, ex);
        } catch (RemoteException ex) {
            Logger.getLogger(DroneClient.class.getName()).log(Level.SEVERE, null, ex);
        } catch (AccessControlException e) {
            System.out.println("Program does not have the required permissions to execute...");
        } catch (NamingException ne) {
            System.out.println("No servers currently host a drone service...");
        }
    }

    public static ArrayList<String> getNetworkMachines()
            throws IOException, InterruptedException, BrokenBarrierException {
        final ArrayList<String> machineList = new ArrayList<>();
        InetAddress             ia          = InetAddress.getLocalHost();
        final StringBuffer      localhost   = new StringBuffer(ia.getHostAddress());

        System.out.println("Querying machines on network...");

        final String          subnet         = localhost.substring(0, localhost.lastIndexOf(".") + 1);
        final boolean         availability[] = new boolean[256];
        final ExecutorService pool           = Executors.newCachedThreadPool();
        final CyclicBarrier   cb             = new CyclicBarrier(256, new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 256; i++) {
                    if (availability[i]) {
                        machineList.add(subnet.toString() + i);
                    }
                }
            }
        });

        for (int i = 0; i < 256; i++) {
            final int j = i;

            pool.submit(new Runnable() {
                @Override
                public void run() {
                    availability[j] = rmiconnect(subnet + j);

                    try {
                        System.out.print("Scanned: " + subnet + j + (availability[j]
                                ? ", IP is a potential server, "
                                : ", IP is not a potential server, "));

                        if (availability[j]) {
                            cb.await(0, TimeUnit.MILLISECONDS);
                        } else {
                            cb.await();
                        }
                    } catch (InterruptedException | BrokenBarrierException | TimeoutException ex) {
                        ex.printStackTrace();
                    }
                }
            });
        }

        System.out.println("Waiting for queries...");
        cb.await();
        pool.shutdown();

        return machineList;
    }

    public static boolean ping(String host, int timeOut) {
        try {
            Process        proc = new ProcessBuilder("ping", host, "-n", "1", "-w", "" + timeOut).start();
            BufferedReader br   = new BufferedReader(new InputStreamReader(proc.getInputStream()));

            proc.waitFor();

            String op = br.readLine();

            while (op != null) {

                // System.out.println("Outputline:" + op);
                if ((op.indexOf("unreachable") != -1) || (op.indexOf("timed out") != -1)) {
                    return false;
                }

                op = br.readLine();
            }
        } catch (IOException e1) {
            System.out.println(e1.getMessage());
            e1.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return true;
    }

    public static boolean rmiconnect(String host) {
        try {
            namingContext.lookup("rmi://" + host + "/" + "DRONE SERVER");
        } catch (NamingException ex) {
            return false;
        }

        return true;
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
